﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using TrainSimulator.Interfaces;
using System.Threading;

namespace TrainSimulator.Model.Engine
{
    public class TrainEngine : ITrainEngine
    {
        private ITrain _train;
        private Queue<ITrack> _tracks;
        private Dictionary<ITrack, double> _trackList;
        private double _trackLength;
        private double _trackLeft;
        public double UpdateFrequency { get; set; }
        public double Accelerating { get; set; }
        public double Deacceleration { get; set; }
        public bool IsRunning { get; set; }
        public double TrackPosition { get; set; }

        public double Interval { get; set; }
        public double ElapsedTime { get; set; }

        public TrainEngine(ITrain train)
        {
            _train = train;
            this.UpdateFrequency = 13;
            this.ElapsedTime = 0;
            this.Accelerating = 9.81;
            this.Deacceleration = 19.62;
            this.TrackPosition = 20; // Start position on track

            _tracks = new Queue<ITrack>();
            _trackList = new Dictionary<ITrack, double>();
            _trackLength = train.CurrentTrack.Length / 2;
            _trackList.Add(train.CurrentTrack, train.Length + _trackLength);
            train.CurrentTrack.IsBlocked = true;
            train.CurrentTrack.BlockingTrain = train;

            ITrack t = train.CurrentTrack.Previous;
            while (t != null && train.Length > _trackLength)
            {
                t.IsBlocked = true;
                t.BlockingTrain = train;
                _trackList.Add(t, train.Length - _trackLength);
                _trackLength += t.Length;
                t = t.Previous;
            }
            _trackLeft = _trackLength - train.Length;
            QueueInvert(_trackList.Keys.ToArray());
        }

        private void QueueInvert(ITrack[] trackList)
        {
            _tracks.Clear();
            for (int i = trackList.Length - 1; i >= 0; i--)
            {
                _tracks.Enqueue(trackList[i]);
            }

        }

        /// <summary>
        /// Updating elapsed time, current speed
        /// </summary>
        public void UpdatePosition(double secs)
        {
            Console.WriteLine("waiting for monitor");
            while (!Monitor.TryEnter(this, 10))
            {
                Console.WriteLine("done waiting");
                this.ElapsedTime += secs;
                _train.Speed += this.Accelerating * secs;
                if (Math.Abs(_train.Speed) > 0.1)
                {
                    double distance = _train.Speed * secs;
                    // Move train the distance
                    _trackLeft -= distance;

                    bool found = false;
                    while (!found && _tracks.Count > 0)
                    {
                        if (_trackLeft <= 0)
                        {
                            RemoveTrack();
                            AddTracks();
                            _trackLeft += _tracks.Peek().Length;
                        }
                        else
                        {
                            found = true;
                        }
                    }
                    AddTracks();
                }
            }
            Monitor.Exit(this);
        }

        private void RemoveTrack()
        {
            ITrack t = _tracks.Dequeue();
            t.IsBlocked = false;
            t.BlockingTrain = null;
            _trackLength -= t.Length;
        }
        private void AddTracks()
        {
                while (_train.Length > _trackLength)
                    AddOneTrack();
                //Train.Reverse = !Train.Reverse;
        }
        private void AddOneTrack()
        {
            ITrack t;

            t = _train.CurrentTrack.NextTrack;
            if (t != null)
            {
                _tracks.Enqueue(t);
                t.IsBlocked = true;
                t.BlockingTrain = _train;
                _trackLength += t.Length;
                _train.CurrentTrack = t;
            }
            else
            {
                throw new Exception("No tracks available");
            }
        }

        public void StartEngine()
        {
            IsRunning = true;
        }

        public void StopEngine()
        {
            IsRunning = false;
        }
    }
}
